home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue33 / undo / Undo.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1998-02-20  |  8.6 KB  |  352 lines

  1. unit Undo;
  2.  
  3. { This unit contains the base classes for a command pattern-based
  4.   undo Stack.
  5.  
  6.   Author : Warren Kovach (wlk@kovcomp.co.uk)
  7.   Published in The Delphi Magazine }
  8.  
  9. interface
  10.  
  11. uses
  12.   Sysutils, Classes, Forms, Menus;
  13.  
  14. {$IFDEF WIN32}
  15.   {$IFDEF VER90}
  16. const
  17.   {$ELSE}
  18. resourcestring
  19.   {$ENDIF}
  20. {$ELSE}
  21. const
  22. {$ENDIF}
  23.   sUndoDescr = 'Undo last action';
  24.   sShortUndoDescr = 'Undo last';
  25.   sRedoDescr = 'Redo last action';
  26.   sShortRedoDescr = 'Redo last';
  27.   sUndoMenu = '&Undo';
  28.   sRedoMenu = '&Redo';
  29.   sNoUndoDescr = 'Command not available; nothing to undo.';
  30.   sNoRedoDescr = 'Command not available; nothing to redo.';
  31.   sStackFull = 'Undo Stack is full; only the last %d actions can be undone';
  32.  
  33. type
  34.   EHiddenProc = class(Exception);
  35.  
  36.   TStackStatus = (ssFull, ssNotFull);
  37.  
  38.   { this is an ancestral type; different forms can inherit
  39.     this and modify it to meet needs of data on that form }
  40.   TUndoItem = class(TObject)
  41.     protected
  42.       function GetUndoDescription : string; virtual;
  43.       function GetShortUndoDescription : string; virtual;
  44.       function GetRedoDescription : string; virtual;
  45.       function GetShortRedoDescription : string; virtual;
  46.       function GetUndoMenuText : string; virtual;
  47.       function GetRedoMenuText : string; virtual;
  48.     public
  49.       procedure DoCommand; virtual; abstract;
  50.       procedure Undo; virtual; abstract;
  51.       procedure Redo; virtual; abstract;
  52.       property UndoDescription : string read GetUndoDescription;
  53.       property ShortUndoDescription : string read GetShortUndoDescription;
  54.       property RedoDescription : string read GetRedoDescription;
  55.       property ShortRedoDescription : string read GetShortRedoDescription;
  56.       property UndoMenuText : string read GetUndoMenuText;
  57.       property RedoMenuText : string read GetRedoMenuText;
  58.   end;
  59.  
  60.   TUndoStack = class(TList)
  61.   private
  62.     FMaxItems : integer;
  63.     procedure HiddenProcExcept;
  64.     procedure SetMaxItems(AMaxItems : integer);
  65.     function GetCurrentItem : TUndoItem;
  66.     function GetCurrentRedoItem : TUndoItem;
  67.     function CanUndo : boolean;
  68.     function CanRedo : boolean;
  69.     procedure UndoClick(Sender: TObject);
  70.     procedure RedoClick(Sender: TObject);
  71.   protected
  72.     CurrentUndo : integer;
  73.   public
  74.     constructor Create(AMaxItems:integer);
  75.     destructor Destroy; override;
  76.     procedure Clear;
  77.     procedure DeleteAndFree(Item : integer);
  78.     function  Submit(Item:TUndoItem) : TStackStatus;
  79.     procedure Undo(Num : integer);
  80.     procedure Redo(Num : integer);
  81.     procedure RemoveLastItem;
  82.     procedure SetUndoMenuItems(var UndoItem,RedoItem : TMenuItem);
  83.     property  MaxItems : integer read FMaxItems write SetMaxItems;
  84.     property  CurrentItem : TUndoItem read GetCurrentItem;
  85.     property  CurrentRedoItem : TUndoItem read GetCurrentRedoItem;
  86.     { disable other access methods }
  87.     procedure Delete(Index: Integer);
  88.     function  Add(Item : Pointer): Integer;
  89.     procedure Insert(Index: Integer; const S: string);
  90.     procedure Move(CurIndex, NewIndex: Integer);
  91.     procedure Exchange(Index1, Index2: Integer);
  92.   end;
  93.  
  94.   TUndoForm = class(TForm)
  95.     public
  96.       UndoStack : TUndoStack;
  97.       constructor Create(AOwner : TComponent); override;
  98.       destructor Destroy; override;
  99.   end;
  100.  
  101. procedure DisableUndoMenus(UndoItem,RedoItem : TMenuItem);
  102.  
  103. implementation
  104.  
  105. function TUndoItem.GetUndoDescription : string;
  106. begin
  107.   Result := sUndoDescr;
  108. end;
  109.  
  110. function TUndoItem.GetShortUndoDescription : string;
  111. begin
  112.   Result := sShortUndoDescr;
  113. end;
  114.  
  115. function TUndoItem.GetRedoDescription : string;
  116. begin
  117.   Result := sRedoDescr;
  118. end;
  119.  
  120. function TUndoItem.GetShortRedoDescription : string;
  121. begin
  122.   Result := sShortRedoDescr;
  123. end;
  124.  
  125. function TUndoItem.GetUndoMenuText : string;
  126. begin
  127.   Result := sUndoMenu;
  128. end;
  129.  
  130. function TUndoItem.GetRedoMenuText : string;
  131. begin
  132.   Result := sRedoMenu;
  133. end;
  134. { -------------------------------------------------- }
  135. constructor TUndoStack.Create(AMaxItems : integer);
  136. begin
  137.   inherited Create;
  138.   FMaxItems := AMaxItems;
  139.   if FMaxItems > MaxListSize then
  140.     FMaxItems := MaxListSize;
  141.   CurrentUndo := -1;
  142. end;
  143.  
  144. destructor TUndoStack.Destroy;
  145. begin
  146.   Clear;
  147.   inherited Destroy;
  148. end;
  149.  
  150. procedure TUndoStack.Clear;
  151. var
  152.   i: Integer;
  153. begin
  154.   for i := pred(Count) downto 0 do DeleteAndFree(i);
  155.   inherited Clear;
  156. end;
  157.  
  158. procedure TUndoStack.DeleteAndFree(Item : integer);
  159. begin
  160.   TUndoItem(Items[Item]).Free;
  161.   inherited Delete(Item);
  162. end;
  163.  
  164. procedure TUndoStack.SetMaxItems(AMaxItems : integer);
  165. var
  166.   i : integer;
  167. begin
  168.   { delete oldest entries if list is shrinking }
  169.   if AMaxItems < FMaxItems then
  170.     for i := 0 to pred(FMaxItems - AMaxItems) do
  171.       DeleteAndFree(0);
  172.   FMaxItems := AMaxItems;
  173.   CurrentUndo := pred(Count);
  174. end;
  175.  
  176. function TUndoStack.GetCurrentItem : TUndoItem;
  177. begin
  178.   if CanUndo then
  179.     Result := Items[CurrentUndo]
  180.   else
  181.     Result := nil;
  182. end;
  183.  
  184. function TUndoStack.GetCurrentRedoItem : TUndoItem;
  185. begin
  186.   if CanRedo then
  187.     Result := Items[succ(CurrentUndo)]
  188.   else
  189.     Result := nil;
  190. end;
  191.  
  192. function TUndoStack.CanUndo : boolean;
  193. begin
  194.   Result := CurrentUndo >= 0;
  195. end;
  196.  
  197. function TUndoStack.CanRedo : boolean;
  198. begin
  199.   Result := (Count > 0) and (CurrentUndo < pred(Count));
  200. end;
  201.  
  202. function TUndoStack.Submit(Item:TUndoItem) : TStackStatus;
  203. var
  204.   i : integer;
  205. begin
  206.   Item.DoCommand;
  207.   { Check to see if we have undone one or more commands }
  208.   if CanRedo then
  209.     { if so then get rid of ones in Redo list (those above the pointer) }
  210.     for i := pred(Count) downto succ(CurrentUndo) do
  211.       DeleteAndFree(i);
  212.   { check if stack is full; if so, pop off oldest command }
  213.   if Count >= MaxItems then begin
  214.     DeleteAndFree(0);
  215.     Result := ssFull;
  216.   end
  217.   else Result := ssNotFull;
  218.   inherited Add(Item);
  219.   { point at top of stack (the size of which may have been modified
  220.     above; can't just inc(CurrentUndo) }
  221.   CurrentUndo := pred(Count);
  222. end;
  223.  
  224. procedure TUndoStack.Undo(Num : integer);
  225. var
  226.   i : integer;
  227. begin
  228.   if CanUndo then
  229.     for i := 1 to Num do begin
  230.       CurrentItem.Undo;
  231.       dec(CurrentUndo);
  232.       if not CanUndo then exit;
  233.     end;
  234. end;
  235.  
  236. procedure TUndoStack.Redo(Num : integer);
  237. var
  238.   i : integer;
  239. begin
  240.   for i := 1 to Num do begin
  241.     if CanRedo then begin
  242.       CurrentRedoItem.Redo;
  243.       inc(CurrentUndo);
  244.     end;
  245.   end;
  246. end;
  247.  
  248. procedure TUndoStack.RemoveLastItem;
  249. begin
  250.   if Count > 0 then begin
  251.     DeleteAndFree(pred(Count));
  252.     dec(CurrentUndo);
  253.   end;
  254. end;
  255.  
  256. procedure TUndoStack.UndoClick(Sender: TObject);
  257. begin
  258.   Undo(1);
  259. end;
  260.  
  261. procedure TUndoStack.RedoClick(Sender: TObject);
  262. begin
  263.   Redo(1);
  264. end;
  265.  
  266. procedure TUndoStack.SetUndoMenuItems(var UndoItem,RedoItem : TMenuItem);
  267. begin
  268.   if CanRedo then begin
  269.     RedoItem.Caption := CurrentRedoItem.RedoMenuText;
  270.     RedoItem.Enabled := true;
  271.     RedoItem.Hint := CurrentRedoItem.RedoDescription;
  272.     RedoItem.OnClick := RedoClick;
  273.   end
  274.   else begin
  275.     DisableUndoMenus(nil,RedoItem);
  276.   end;
  277.   if CurrentItem <> nil then begin
  278.     UndoItem.Caption := CurrentItem.UndoMenuText;
  279.     UndoItem.Enabled := true;
  280.     UndoItem.Hint := CurrentItem.UndoDescription;
  281.     UndoItem.OnClick := UndoClick;
  282.   end
  283.   else
  284.     DisableUndoMenus(UndoItem,nil);
  285. end;
  286.  
  287.  
  288. { disable other access methods }
  289. procedure TUndoStack.HiddenProcExcept;
  290. begin
  291.   Raise EHiddenProc.Create('Error - access to stack only allowed through Submit and Clear');
  292. end;
  293.  
  294. function TUndoStack.Add(Item : Pointer): Integer;
  295. begin
  296.   HiddenProcExcept;
  297. end;
  298.  
  299. procedure TUndoStack.Delete(Index: Integer);
  300. begin
  301.   HiddenProcExcept;
  302. end;
  303.  
  304. procedure TUndoStack.Insert(Index: Integer; const S: string);
  305. begin
  306.   HiddenProcExcept;
  307. end;
  308.  
  309. procedure TUndoStack.Move(CurIndex, NewIndex: Integer);
  310. begin
  311.   HiddenProcExcept;
  312. end;
  313.  
  314. procedure TUndoStack.Exchange(Index1, Index2: Integer);
  315. begin
  316.   HiddenProcExcept;
  317. end;
  318.  
  319. { ----------------------------------------------- }
  320.  
  321. constructor TUndoForm.Create(AOwner : TComponent);
  322. begin
  323.   inherited Create(AOwner);
  324.   UndoStack := TUndoStack.Create(100);
  325. end;
  326.  
  327. destructor TUndoForm.Destroy;
  328. begin
  329.   UndoStack.Free;
  330.   inherited destroy;
  331. end;
  332.  
  333. { ----------------------------------------------- }
  334.  
  335. procedure DisableUndoMenus(UndoItem,RedoItem:TMenuItem);
  336. begin
  337.   if UndoItem <> nil then begin
  338.     UndoItem.Caption := sUndoMenu;
  339.     UndoItem.Enabled := false;
  340.     UndoItem.Hint := sNoUndoDescr;
  341.     UndoItem.OnClick := nil;
  342.   end;
  343.   if RedoItem <> nil then begin
  344.     RedoItem.Caption := sRedoMenu;
  345.     RedoItem.Enabled := false;
  346.     RedoItem.Hint := sNoRedoDescr;
  347.     RedoItem.OnClick := nil;
  348.   end;
  349. end;
  350.  
  351. end.
  352.